/*
 * Copyright (c) 2011, Nikolaus Moll (developer (at) miblounge (dot) net).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * * Neither the name of the Jombi project nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL JOMBI BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package net.miblounge.jombi.impl.widgets;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import net.miblounge.jombi.impl.util.AlignmentConvert;
import net.miblounge.jombi.impl.util.ColorConvert;
import net.miblounge.jombi.impl.util.FontProvider;
import net.miblounge.jombi.impl.util.PositionConvert;
import net.miblounge.jombi.impl.util.TableSelectionPolicyConvert;

import org.jowidgets.common.color.IColorConstant;
import org.jowidgets.common.model.ITableCell;
import org.jowidgets.common.model.ITableColumnModelListener;
import org.jowidgets.common.model.ITableColumnModelObservable;
import org.jowidgets.common.model.ITableColumnModelSpi;
import org.jowidgets.common.model.ITableColumnSpi;
import org.jowidgets.common.model.ITableDataModel;
import org.jowidgets.common.model.ITableDataModelListener;
import org.jowidgets.common.model.ITableDataModelObservable;
import org.jowidgets.common.types.AlignmentHorizontal;
import org.jowidgets.common.types.Dimension;
import org.jowidgets.common.types.Markup;
import org.jowidgets.common.types.Position;
import org.jowidgets.common.types.TablePackPolicy;
import org.jowidgets.common.widgets.controller.IPopupDetectionListener;
import org.jowidgets.common.widgets.controller.ITableCellEditEvent;
import org.jowidgets.common.widgets.controller.ITableCellEditorListener;
import org.jowidgets.common.widgets.controller.ITableCellListener;
import org.jowidgets.common.widgets.controller.ITableCellPopupDetectionListener;
import org.jowidgets.common.widgets.controller.ITableColumnListener;
import org.jowidgets.common.widgets.controller.ITableColumnPopupDetectionListener;
import org.jowidgets.common.widgets.controller.ITableSelectionListener;
import org.jowidgets.spi.impl.controller.PopupDetectionObservable;
import org.jowidgets.spi.impl.controller.TableCellEditEvent;
import org.jowidgets.spi.impl.controller.TableCellEditorObservable;
import org.jowidgets.spi.impl.controller.TableCellObservable;
import org.jowidgets.spi.impl.controller.TableCellPopupDetectionObservable;
import org.jowidgets.spi.impl.controller.TableCellPopupEvent;
import org.jowidgets.spi.impl.controller.TableColumnObservable;
import org.jowidgets.spi.impl.controller.TableColumnPopupDetectionObservable;
import org.jowidgets.spi.impl.controller.TableColumnPopupEvent;
import org.jowidgets.spi.impl.controller.TableColumnResizeEvent;
import org.jowidgets.spi.impl.controller.TableSelectionObservable;
import org.jowidgets.spi.widgets.ITableSpi;
import org.jowidgets.spi.widgets.setup.ITableSetupSpi;

import com.trolltech.qt.core.QEvent;
import com.trolltech.qt.core.QModelIndex;
import com.trolltech.qt.core.QObject;
import com.trolltech.qt.core.QPoint;
import com.trolltech.qt.core.Qt;
import com.trolltech.qt.core.Qt.AlignmentFlag;
import com.trolltech.qt.gui.QAbstractItemView.SelectionBehavior;
import com.trolltech.qt.gui.QAbstractTableModel;
import com.trolltech.qt.gui.QContextMenuEvent;
import com.trolltech.qt.gui.QFrame;
import com.trolltech.qt.gui.QHeaderView;
import com.trolltech.qt.gui.QHeaderView.ResizeMode;
import com.trolltech.qt.gui.QItemDelegate;
import com.trolltech.qt.gui.QItemSelection;
import com.trolltech.qt.gui.QItemSelectionModel.SelectionFlag;
import com.trolltech.qt.gui.QLineEdit;
import com.trolltech.qt.gui.QStyleOptionViewItem;
import com.trolltech.qt.gui.QTableView;
import com.trolltech.qt.gui.QWidget;

public class TableImpl extends QtControl implements ITableSpi {

	private final QTableView table;
	private final QHeaderView header;
	private final TableModel tableModel;

	private final ITableDataModel dataModel;
	private final ITableColumnModelSpi columnModel;

	private final PopupDetectionObservable popupDetectionObservable;
	private final TableCellObservable tableCellObservable;
	private final TableCellPopupDetectionObservable tableCellPopupDetectionObservable;
	private final TableColumnPopupDetectionObservable tableColumnPopupDetectionObservable;
	private final TableSelectionObservable tableSelectionObservable;
	private final TableCellEditorObservable tableCellEditorObservable;
	private final TableColumnObservable tableColumnObservable;

	private final TableModelListener tableModelListener;
	private final TableColumnModelListener tableColumnModelListener;

	private final List<PackDescription> packOnShowList = new LinkedList<PackDescription>();

	public TableImpl(final Object parentUiReference, final ITableSetupSpi setup) {
		super(new QTableView((QWidget) parentUiReference));

		getUiReference().setFrameShape(QFrame.Shape.NoFrame);
		getUiReference().setItemDelegate(new ItemDelegate());

		dataModel = setup.getDataModel();
		columnModel = setup.getColumnModel();

		this.table = getUiReference();
		this.header = table.horizontalHeader();

		final int rowHeight = getUiReference().fontMetrics().height() + 2;
		table.verticalHeader().setDefaultSectionSize(rowHeight);
		table.verticalHeader().setVisible(false);

		if (!setup.isHeaderVisible()) {
			header.setVisible(false);
			//header.setPreferredSize(new java.awt.Dimension(-1, 0)); 
		} else {
			header.setHighlightSections(false);
		}

		this.popupDetectionObservable = new PopupDetectionObservable();
		this.tableCellObservable = new TableCellObservable();
		this.tableCellPopupDetectionObservable = new TableCellPopupDetectionObservable();
		this.tableColumnPopupDetectionObservable = new TableColumnPopupDetectionObservable();
		this.tableSelectionObservable = new TableSelectionObservable();
		this.tableCellEditorObservable = new TableCellEditorObservable();
		this.tableColumnObservable = new TableColumnObservable();

		this.tableModelListener = new TableModelListener();
		this.tableColumnModelListener = new TableColumnModelListener();

		this.tableModel = new TableModel(getUiReference(), setup.getColumnModel(), setup.getDataModel());

		table.setModel(tableModel);

		header.setMovable(setup.getColumnsMoveable());
		if (!setup.getColumnsResizeable()) {
			header.setResizeMode(ResizeMode.Fixed);
		}

		// getUiReference().cellPressed.connect(this, "tableCellPressed(int, int)");
		// getUiReference().cellDoubleClicked.connect(this, "tableCellDoubleClicked(int, int)");
		// ggetUiReference().itemSelectionChanged.connect(this, "tableItemSelectionChanged()");

		table.setSelectionBehavior(SelectionBehavior.SelectRows);
		table.setSelectionMode(TableSelectionPolicyConvert.convert(setup.getSelectionPolicy()));

		addSelectionListener();
		header.sectionMoved.connect(this, "headerPermutationChanged(int, int, int)");
		header.sectionResized.connect(this, "headerResized(int, int, int)");

		table.itemDelegate().commitData.connect(this, "editorCommit(Object)");

		final QObject eventFilter = new QObject() {
			@Override
			public boolean eventFilter(final QObject source, final QEvent event) {
				if (event.type() == QEvent.Type.Show) {
					if (getUiReference().isVisible()) {
						packIfNecessesary();
					}
				}

				if (event.type() == QEvent.Type.ContextMenu) {
					final QContextMenuEvent contextMenuEvent = (QContextMenuEvent) event;
					final Position position = PositionConvert.convert(contextMenuEvent.pos());

					final QPoint localPos = contextMenuEvent.pos().subtract(new QPoint(0, header.height() + 1));
					if (localPos.y() < 0) {
						final int colIndex = table.columnAt(localPos.x());
						tableColumnPopupDetectionObservable.firePopupDetected(new TableColumnPopupEvent(colIndex, position));
						return true;
					}

					final QModelIndex index = table.indexAt(localPos);
					if (index != null) {
						final int rowIndex = index.row();
						final int colIndex = index.column();
						tableCellPopupDetectionObservable.firePopupDetected(new TableCellPopupEvent(rowIndex, colIndex, position));
						return true;
					} else {
						popupDetectionObservable.firePopupDetected(position);
						return true;
					}
				}
				return super.eventFilter(source, event);
			}
		};
		table.installEventFilter(eventFilter);
	}

	private void addSelectionListener() {
		table.selectionModel().selectionChanged.connect(this, "selectionChanged(QItemSelection, QItemSelection)");
	}

	private void removeSelectionListener() {
		table.selectionModel().selectionChanged.disconnect(this, "selectionChanged(QItemSelection, QItemSelection)");
	}

	@Override
	public QTableView getUiReference() {
		return (QTableView) super.getUiReference();
	}

	protected void editorCommit(final Object editor) {
		if (editor instanceof QLineEdit) {
			final QLineEdit lineEdit = (QLineEdit) editor;

			final int row = table.currentIndex().row();
			final int column = table.currentIndex().column();
			final String text = lineEdit.text();
			final ITableCellEditEvent editEvent = new TableCellEditEvent(row, column, text);

			tableCellEditorObservable.fireEditFinished(editEvent);
		} else {
			throw new IllegalStateException("Cannot handle editor of type '" + editor.getClass().toString() + "'");
		}
	}

	protected void headerPermutationChanged(final int logicalIndex, final int oldVisualIndex, final int newVisualIndex) {
		tableColumnObservable.fireColumnPermutationChanged();
	}

	protected void headerResized(final int logicalIndex, final int oldSize, final int newSize) {
		tableColumnObservable.fireColumnResized(new TableColumnResizeEvent(logicalIndex, newSize));
	}

	protected void selectionChanged(final QItemSelection selected, final QItemSelection deselected) {
		dataModel.setSelection(getSelection());
		tableSelectionObservable.fireSelectionChanged();
	}

	@Override
	public void addPopupDetectionListener(final IPopupDetectionListener listener) {
		popupDetectionObservable.addPopupDetectionListener(listener);
	}

	@Override
	public void removePopupDetectionListener(final IPopupDetectionListener listener) {
		popupDetectionObservable.removePopupDetectionListener(listener);
	}

	@Override
	public void resetFromModel() {
		//unregister listeners
		final ITableDataModelObservable dataModelObservable = dataModel.getTableDataModelObservable();
		if (dataModelObservable != null) {
			dataModelObservable.removeDataModelListener(tableModelListener);
		}
		final ITableColumnModelObservable columnModelObservable = columnModel.getTableColumnModelObservable();
		if (columnModelObservable != null) {
			columnModelObservable.removeColumnModelListener(tableColumnModelListener);
		}

		tableModel.dataChanged();

		//set the current selection
		setSelection(dataModel.getSelection());

		//register listeners
		if (dataModelObservable != null) {
			dataModelObservable.addDataModelListener(tableModelListener);
		}
		if (columnModelObservable != null) {
			columnModelObservable.addColumnModelListener(tableColumnModelListener);
		}

		//make changes appear in table view
		table.repaint();
	}

	@Override
	public Position getCellPosition(final int rowIndex, final int columnIndex) {
		return new Position(getUiReference().rowViewportPosition(rowIndex), getUiReference().columnViewportPosition(columnIndex));
	}

	@Override
	public Dimension getCellSize(final int rowIndex, final int columnIndex) {
		return new Dimension(getUiReference().rowHeight(rowIndex), getUiReference().columnWidth(columnIndex));
	}

	@Override
	public ArrayList<Integer> getColumnPermutation() {
		final int columnCount = columnModel.getColumnCount();
		final ArrayList<Integer> result = new ArrayList<Integer>(columnCount);
		for (int i = 0; i < columnCount; i++) {
			result.add(header.logicalIndex(i));
		}

		return result;
	}

	@Override
	public void setColumnPermutation(final List<Integer> permutation) {
		int columnIndex = 0;
		for (final Integer permutatedIndex : permutation) {
			final int visualFrom = header.visualIndex(permutatedIndex);
			final int visualTo = columnIndex;
			header.moveSection(visualFrom, visualTo);
			columnIndex++;
		}
	}

	@Override
	public ArrayList<Integer> getSelection() {
		final ArrayList<Integer> result = new ArrayList<Integer>();
		for (final QModelIndex index : table.selectionModel().selection().indexes()) {
			if (result.contains(index.row())) {
				continue;
			}

			result.add(index.row());
		}
		Collections.sort(result);
		return result;
	}

	@Override
	public void setSelection(final List<Integer> selection) {
		if (!getSelection().equals(selection)) {
			final int columnCount = columnModel.getColumnCount();
			table.selectionModel().clearSelection();

			if (selection != null) {
				for (final int row : selection) {
					for (int column = 0; column <= columnCount; column++) {
						final QModelIndex index = tableModel.index(row, column);
						table.selectionModel().setCurrentIndex(index, SelectionFlag.Select);
					}
				}
			}
		}
	}

	@Override
	public void pack(final TablePackPolicy policy) {
		if (!getUiReference().isVisible()) {
			packOnShowList.clear();
			packOnShowList.add(PackDescription.packAll(policy));
		} else {
			final RowRange range = getRowRange(policy);
			for (int columnIndex = 0; columnIndex < columnModel.getColumnCount(); columnIndex++) {
				pack(columnIndex, range, policy);
			}
		}
	}

	@Override
	public void pack(final int columnIndex, final TablePackPolicy policy) {
		if (!getUiReference().isVisible()) {
			packOnShowList.add(PackDescription.packColumn(columnIndex, policy));
		} else {
			pack(columnIndex, getRowRange(policy), policy);
		}
	}

	private void packIfNecessesary() {
		if (packOnShowList.size() > 0) {
			for (final PackDescription packDescription : packOnShowList) {
				if (packDescription.isSingleColumn()) {
					final RowRange range = getRowRange(packDescription.getPolicy());
					pack(packDescription.getColumn(), range, packDescription.getPolicy());
				} else {
					pack(packDescription.getPolicy());
				}
			}
			packOnShowList.clear();
		}
	}

	private void pack(final int columnIndex, final RowRange rowRange, final TablePackPolicy policy) {
		int maxWidth = 0;

		if (policy.considerData()) {
			for (int rowIndex = rowRange.getStartIndex(); rowIndex <= rowRange.getEndIndex(); rowIndex++) {
				final Object data = tableModel.data(rowIndex, columnIndex);
				if (data instanceof String) {
					final String text = (String) data;
					maxWidth = Math.max(maxWidth, getUiReference().fontMetrics().width(text) + 3);
				} else {
					throw new IllegalStateException("Unkown data type '" + data.getClass().toString() + "'");
				}
			}
		}

		if (policy.considerHeader()) {
			final Object data = tableModel.headerData(columnIndex, Qt.Orientation.Horizontal);
			if (data instanceof String) {
				final String text = (String) data;
				maxWidth = Math.max(maxWidth, getUiReference().fontMetrics().width(text) + 1);
			} else {
				throw new IllegalStateException("Unkown data type '" + data.getClass().toString() + "'");
			}
		}

		header.resizeSection(columnIndex, maxWidth + 10);
	}

	private RowRange getRowRange(final TablePackPolicy policy) {
		if (policy.considerAllData()) {
			return new RowRange(0, dataModel.getRowCount() - 1);
		} else {
			final int firstVisibleRowIndex = table.rowAt(0);
			int lastVisibleRowIndex = table.rowAt(getUiReference().height()
				- header.height()
				- getUiReference().horizontalScrollBar().height()
				- 1);

			if (lastVisibleRowIndex == -1) {
				lastVisibleRowIndex = dataModel.getRowCount() - 1;
			}
			return new RowRange(firstVisibleRowIndex, lastVisibleRowIndex);
		}
	}

	@Override
	public void addTableSelectionListener(final ITableSelectionListener listener) {
		tableSelectionObservable.addTableSelectionListener(listener);
	}

	@Override
	public void removeTableSelectionListener(final ITableSelectionListener listener) {
		tableSelectionObservable.removeTableSelectionListener(listener);
	}

	@Override
	public void addTableCellListener(final ITableCellListener listener) {
		tableCellObservable.addTableCellListener(listener);
	}

	@Override
	public void removeTableCellListener(final ITableCellListener listener) {
		tableCellObservable.removeTableCellListener(listener);
	}

	@Override
	public void addTableCellPopupDetectionListener(final ITableCellPopupDetectionListener listener) {
		tableCellPopupDetectionObservable.addTableCellPopupDetectionListener(listener);
	}

	@Override
	public void removeTableCellPopupDetectionListener(final ITableCellPopupDetectionListener listener) {
		tableCellPopupDetectionObservable.removeTableCellPopupDetectionListener(listener);
	}

	@Override
	public void addTableCellEditorListener(final ITableCellEditorListener listener) {
		tableCellEditorObservable.addTableCellEditorListener(listener);
	}

	@Override
	public void removeTableCellEditorListener(final ITableCellEditorListener listener) {
		tableCellEditorObservable.removeTableCellEditorListener(listener);
	}

	@Override
	public void addTableColumnListener(final ITableColumnListener listener) {
		tableColumnObservable.addTableColumnListener(listener);
	}

	@Override
	public void removeTableColumnListener(final ITableColumnListener listener) {
		tableColumnObservable.removeTableColumnListener(listener);
	}

	@Override
	public void addTableColumnPopupDetectionListener(final ITableColumnPopupDetectionListener listener) {
		tableColumnPopupDetectionObservable.addTableColumnPopupDetectionListener(listener);
	}

	@Override
	public void removeTableColumnPopupDetectionListener(final ITableColumnPopupDetectionListener listener) {
		tableColumnPopupDetectionObservable.addTableColumnPopupDetectionListener(listener);
	}

	final class TableModelListener implements ITableDataModelListener {

		@Override
		public void rowsAdded(final int[] rowIndices) {
			if (tableModel != null) {
				tableModel.rowsAdded(rowIndices);
			}
		}

		@Override
		public void rowsRemoved(final int[] rowIndices) {
			if (tableModel != null) {
				tableModel.rowsRemoved(rowIndices);
			}
		}

		@Override
		public void rowsChanged(final int[] rowIndices) {
			if (tableModel != null) {
				tableModel.rowsChanged(rowIndices);
			}
		}

		@Override
		public void dataChanged() {
			if (tableModel != null) {
				tableModel.dataChanged();
			}
		}

		@Override
		public void selectionChanged() {
			setSelection(dataModel.getSelection());
		}

	}

	final class TableColumnModelListener implements ITableColumnModelListener {

		@Override
		public void columnsAdded(final int[] columnIndices) {
			if (tableModel != null) {
				tableModel.columnsAdded(columnIndices);
			}
		}

		@Override
		public void columnsRemoved(final int[] columnIndices) {
			if (tableModel != null) {
				tableModel.columnsRemoved(columnIndices);
			}
		}

		@Override
		public void columnsChanged(final int[] columnIndices) {
			if ((header != null) && (header.isVisible())) {
				for (final int columnIndex : columnIndices) {
					header.headerDataChanged(Qt.Orientation.Horizontal, columnIndex, columnIndex);
				}
			}
		}
	}

	public class TableModel extends QAbstractTableModel {

		private final ITableColumnModelSpi tableColumnModel;
		private final ITableDataModel tableDataModel;
		private final QTableView table;

		public TableModel(
			final QTableView table,
			final ITableColumnModelSpi tableColumnModel,
			final ITableDataModel tableDataModel) {
			this.table = table;
			this.tableColumnModel = tableColumnModel;
			this.tableDataModel = tableDataModel;
		}

		@Override
		public int columnCount(final QModelIndex arg0) {
			return tableColumnModel.getColumnCount();
		}

		@Override
		public int rowCount(final QModelIndex arg0) {
			return tableDataModel.getRowCount();
		}

		@Override
		public Qt.ItemFlags flags(final QModelIndex index) {
			final ITableCell cell = tableDataModel.getCell(index.row(), index.column());

			// TODO
			final Qt.ItemFlags result = super.flags(index);
			if (cell.isEditable()) {
				result.set(Qt.ItemFlag.ItemIsEditable);
			}
			return result;
		}

		@Override
		public Object headerData(final int section, final Qt.Orientation orientation, final int role) {
			if (orientation == Qt.Orientation.Vertical) {
				return null;
			}

			final ITableColumnSpi column = tableColumnModel.getColumn(section);
			switch (role) {
				case Qt.ItemDataRole.DisplayRole:
					return column.getText();

				case Qt.ItemDataRole.TextAlignmentRole:
					return AlignmentFlag.AlignLeft;
					// to align header like its content:
					//	if (column.getAlignment() == null) {
					//		return AlignmentFlag.AlignLeft;
					//	}
					//	return AlignmentConvert.convert(column.getAlignment());

				case Qt.ItemDataRole.ToolTipRole:
					return column.getToolTipText();

				default:
					return null;
			}

		}

		@Override
		public Object data(final QModelIndex index, final int role) {
			final ITableCell cell = tableDataModel.getCell(index.row(), index.column());

			switch (role) {
				case Qt.ItemDataRole.BackgroundRole:
					final IColorConstant backgroundColor = cell.getBackgroundColor();
					if (backgroundColor == null) {
						return null;
					}
					return ColorConvert.convert(backgroundColor);

				case Qt.ItemDataRole.DisplayRole:
					return cell.getText();

				case Qt.ItemDataRole.EditRole:
					// TODO NM return boolean values for checkboxes ?
					if (cell.isEditable()) {
						return cell.getText();
					}
					return null;

				case Qt.ItemDataRole.FontRole:
					final Markup markup = cell.getMarkup();
					if (markup == null) {
						return null;
					}
					return FontProvider.deriveFont(table.font(), markup);

				case Qt.ItemDataRole.ForegroundRole:
					final IColorConstant foregroundColor = cell.getForegroundColor();
					if (foregroundColor == null) {
						return null;
					}
					return ColorConvert.convert(foregroundColor);

				case Qt.ItemDataRole.TextAlignmentRole:
					final AlignmentHorizontal alignment = columnModel.getColumn(index.column()).getAlignment();
					return AlignmentConvert.convert(alignment);

				case Qt.ItemDataRole.ToolTipPropertyRole:
					return null;
				case Qt.ItemDataRole.ToolTipRole:
					return null;
				default:
					return null;
			}

		}

		private ArrayList<Integer> inverseSort(final int[] values) {
			final ArrayList<Integer> result = new ArrayList<Integer>(values.length);
			for (final int row : values) {
				result.add(row);
			}
			Collections.sort(result, new Comparator<Integer>() {

				@Override
				public int compare(final Integer o1, final Integer o2) {
					return o1 - o2;
				}
			});
			return result;
		}

		public void rowsRemoved(final int[] rowIndices) {
			final ArrayList<Integer> rows = inverseSort(rowIndices);
			for (final int row : rows) {
				beginRemoveRows(null, row, row);
				endRemoveRows();
			}
		}

		public void rowsAdded(final int[] rowIndices) {
			final ArrayList<Integer> rows = inverseSort(rowIndices);
			for (final int row : rows) {
				beginInsertRows(null, row, row);
				endInsertRows();
			}
		}

		public void rowsChanged(final int[] rowIndices) {
			final int columnCount = tableColumnModel.getColumnCount();
			for (final int row : rowIndices) {
				dataChanged.emit(index(row, 0), index(row, columnCount));
			}
		}

		public void dataChanged() {
			removeSelectionListener();
			tableModel.reset();
			setSelection(dataModel.getSelection());
			addSelectionListener();
		}

		public void columnsAdded(final int[] columnIndices) {
			//Trivial case. If nothing added return
			if (columnIndices.length == 0) {
				return;
			}

			//Sort the indices to add
			final ArrayList<Integer> columns = inverseSort(columnIndices);
			for (final int columnIndex : columns) {
				beginInsertColumns(null, columnIndex, columnIndex);
				endInsertColumns();
			}
		}

		public void columnsRemoved(final int[] columnIndices) {
			//Trivial case. If nothing added return
			if (columnIndices.length == 0) {
				return;
			}

			//Sort the indices to add
			final ArrayList<Integer> columns = inverseSort(columnIndices);
			for (final int columnIndex : columns) {
				beginRemoveColumns(null, columnIndex, columnIndex);
				endRemoveColumns();
			}
		}
	}

	final class ItemDelegate extends QItemDelegate {

		private int row;
		private int column;
		private QLineEdit lineEdit;
		private String oldText;

		@Override
		public QWidget createEditor(final QWidget parent, final QStyleOptionViewItem option, final QModelIndex index) {
			lineEdit = (QLineEdit) super.createEditor(parent, option, index);
			row = index.row();
			column = index.column();
			oldText = lineEdit.text();
			lineEdit.textEdited.connect(this, "textEdited(String)");

			return lineEdit;
		}

		protected void textEdited(final String text) {
			final ITableCellEditEvent editEvent = new TableCellEditEvent(row, column, text);
			final boolean veto = tableCellEditorObservable.fireOnEdit(editEvent);

			if (veto) {
				lineEdit.setText(oldText);
				//TODO MG handle veto
			} else {
				oldText = text;
			}
		}
	}

	final class RowRange {

		private final int startIndex;
		private final int endIndex;

		public RowRange(final int startIndex, final int endIndex) {
			super();
			this.startIndex = startIndex;
			this.endIndex = endIndex;
		}

		public int getStartIndex() {
			return startIndex;
		}

		public int getEndIndex() {
			return endIndex;
		}

	}

	static final class PackDescription {
		private final TablePackPolicy policy;
		private final int column;

		private PackDescription(final int column, final TablePackPolicy policy) {
			this.column = column;
			this.policy = policy;
		}

		public static PackDescription packColumn(final int column, final TablePackPolicy policy) {
			return new PackDescription(column, policy);
		}

		public static PackDescription packAll(final TablePackPolicy policy) {
			return new PackDescription(-1, policy);
		}

		public boolean isSingleColumn() {
			return (column >= 0);
		}

		public int getColumn() {
			return column;
		}

		public TablePackPolicy getPolicy() {
			return policy;
		}
	}

	@Override
	public void setToolTipText(final String toolTip) {
		// TODO NM implement
	}

	@Override
	public void showSelection() {
		// TODO NM implement
	}

	@Override
	public boolean isColumnPopupDetectionSupported() {
		// TODO NM implement
		return false;
	}
}
